<?php

/**
 * @package AMFramework
 * @subpackage Developer
 * @author Marcu Alexandru
 * @version 1.0 $Build 305b  
 */
defined('_INDEX') or die('Restricted');

// Debug Globals
define('D_JOURNAL', 110);
define('D_ERROR', E_USER_ERROR);
define('D_WARN', E_USER_WARNING);
define('D_NOTE', E_USER_NOTICE);

// Error Reporting Globals
define('ER_MAX', -1);
define('ER_HIGH', E_ALL);
define('ER_NORMAL', E_ALL ^ E_NOTICE);
define('ER_LOW', E_ERROR);
define('ER_NONE', 0);

class Amfw_Debug 
{
	const USECDIV = 1000000; // Number of microseconds in a second
	
	private static $_errorLog 		= null;
	private static $_errorReporting= 0;
	private static $_errorDisplay 	= array();
	private static $_runtimeLog 	= array();
	private static $_globalQueryCounter = 0;
	private static $_errorLogEnabled 		= false;
	private static $_performanceLogEnabled = false;
	private static $_runtimeLogEnabled 	= false;
	// Options
	private static $runtimeLogFilter = null;
	
	// Timer arrays
	private static $timeRunning = array(); //State of the timers
	private static $timeQueue = array(); //Contains the queue of times
	private static $timeDebugStarted;	

	public static function create($profile) {
		self::readConfig($profile);
		self::initProfile();
		self::timerStart('debug-internal');
		self::$timeDebugStarted = microtime(true);
	}
	private static function readConfig($profile) {
		// Reads the debug configuration from Config/Debug.xml
		$xml_file = CONFIG_PATH.DS.'Debug.xml';
		if(!file_exists($xml_file)) 
			return null;
		
		$xml = simplexml_load_file($xml_file);
		$data = $xml->$profile;

		if(empty($data)) {
			self::_triggerError('Unable to find Debug.xml configuration for the selected debug profile',D_ERROR);
			return;
		}

		// Error reporting
		self::$_errorReporting = (int) $data->errorReporting;

		// Error log
		self::$_errorLogEnabled = $data->errorLog['enabled'] == '0' ? false : true ;
		if(self::$_errorLogEnabled) {
			$tmp = (string) $data->errorLog->path;
			$tmp = str_ireplace('%root',BASE_PATH,$tmp);
			$tmp = str_ireplace('/',DS,$tmp);
			self::$_errorLog = $tmp;
		}
		// Performace and runtime logs
		self::$_performanceLogEnabled = $data->performanceLog['enabled'] == '0' ? false : true ;
		self::$_runtimeLogEnabled = $data->runtimeLog['enabled'] == '0' ? false : true ;
		
		// Display errors
		self::$_errorDisplay['runtime'] = (int) $data->errorsDisplay->runtime;
		self::$_errorDisplay['startup'] = (int) $data->errorsDisplay->startup;
	}
	private static function initProfile() {
		// Aplica setarile citite din XML 
		switch( self::$_errorReporting ) 
		{
			default	 : 	$error = ER_MAX; 	break;
			case 	4:	$error = ER_MAX; 	break;
			case 	3:	$error = ER_HIGH; 	break;
			case 	2:	$error = ER_NORMAL; break;
			case 	1:	$error = ER_LOW; 	break;
			case 	0:	$error = ER_NONE; 	break;
		}	error_reporting($error);
		
		$max_errorlog_size = 1048576 * 2; //2 MB
		if(file_exists(self::$_errorLog)) {
			if(filesize(self::$_errorLog) > $max_errorlog_size) {
				self::$_errorLogEnabled = false;
			}
		}
		
		if(self::$_errorLogEnabled === true) {
			ini_set('error_log',self::$_errorLog);
		}
		ini_set('display_errors',self::$_errorDisplay['runtime']);
		ini_set('display_startup_errors', self::$_errorDisplay['startup']);
 	}

 	public static function timerStart($name) {
		self::$timeRunning[$name] = false;
		self::_pushTime('start '.$name);
	}
	public static function timerStop($name) {
		self::_pushTime('end '.$name);
	}
	public static function timerReset() {
		self::$timeQueue = array();
	}
	public static function getTimerResults($name, $format = 'secs')
	{
		if(self::$timeRunning[$name] === true) {
			self::_triggerError('Forcing timer to stop', E_USER_NOTICE);
			self::timerStop($name);
		}
		// Reset all values
		$sec = 0;
		$usec = 0;
		// Start and end times
		$start = self::$timeQueue[$name]['start'];
		$end = self::$timeQueue[$name]['end'];
		
		// Calculate difference between start and end seconds;
		$sec_diff = $end['sec'] - $start['sec'];
		// If starting and finishing seconds are the same
		if($sec_diff === 0) {
			//Only add the microseconds difference
			$usec += ($end['usec'] - $start['usec']);
		}
		else {
			//Add the difference in seconds (compensate for microseconds)
			$sec += $sec_diff - 1;
			//Add the difference time between start and end microseconds
			$usec += (self::USECDIV - $start['usec']) + $end['usec'];
		}
		if($usec > self::USECDIV) {
			//Move the full second microseconds to the seconds part
			$sec += (int) floor($usec / self::USECDIV);

			// Keep only the microseconds that are over the self::USECDIV
			$usec = $usec % self::USECDIV;
		}
		switch ($format) {
		case 'micros':
			return ($sec * self::USECDIV) + $usec;

		case 'millis':
			return ($sec * 1000) + (int) round($usec / 1000, 0);

		case 'secs':
		default:
			return (float) $sec + (float) ($usec / self::USECDIV);
		}
	}
	public static function toJournal($message, $type = 110, $code = 0)
	{	// When using this method we're logging both errors and journal entries (recommended)
		// Creating the timediff (the current moment of the application)
		$timediff = microtime(true)-self::$timeDebugStarted;
		$item = new stdClass();
		$item->text = $message;
		$item->code = $code;
		$item->time = $timediff;
		
		self::$_runtimeLog[] = $item;

		if(in_array($type,array(D_ERROR, D_WARN, D_NOTE))) {
			self::_triggerError($message,$type);
		}
	}
	private static function _triggerError($message, $type) {
		trigger_error($message, $type);
	}
	
	public static function incQueryCount() {
		if(self::$_performanceLogEnabled === true) {
			self::$_globalQueryCounter++;
		}
	}
	public static function getQueryCount() {
		return self::$_globalQueryCounter;
	}
	public static function setErrorFilter($filter) {
		self::$runtimeLogFilter = $filter;
	}
	
	public static function getRuntimeLog($type = 'html_table', $filter_by_code = null) 
	{
		if(empty(self::$_runtimeLog)||!is_array(self::$_runtimeLog)) 
			return false;	//If we don`t have an array or the resource is empty, return false

		$log = array();
		if($filter_by_code) {	// Filtering by code
			foreach(self::$_runtimeLog as &$entry) {
				if($entry->code == $filter_by_code): $log[] = $entry; endif;
			}//~!foreach
		}//~!if
		else $log = self::$_runtimeLog; // No code filtering
		
		switch($type) {
			case 'array':	//--------------------------------------- 
				return $log;

			case 'html_table':	//-----------------------------------
				$html = '<table id="debug_table">';
				foreach ($log as $entry) {
					$html .= '<tr>';
					$html .= '<td>'.sprintf('|<B> Time:</B> %.5f',$entry->time).'</td>';
					$html .= '<td>'.sprintf('|<B> Entry:</B> %s',$entry->text).'</td>';
					$html .= '<td>'.sprintf('|<B> Code:</B> %d', $entry->code).'</td>';
					$html .= '</tr>';
				}
				$html .= '</table>';
				return $html;
				break;
		}
	}
	public static function close() {
		self::timerStop('debug-internal');
	}
	
	public static function getPerformanceLog($type = 'html_table') {
		$log = array();
		$log['Query Count'] = self::getQueryCount();
		$log['Approx. Execution Time'] = self::getTimerResults('debug-internal');
		
		switch($type) {
			case 'array':
				return $log;
			
			case 'html_table':
				$html = '<table id="debug_table">';
				foreach($log as $stat => $value) {
					$html .= '<tr>';
					$html .= sprintf('<td>| <B>%s:</B></td>',$stat);
					$html .= sprintf('<td>| <B>%s</B></td>',$value);
					$html .= '</tr>';
				}
				$html .= '</table>';
				return $html;
		}
	}
	
	//html / plain
	//display / file / both
	public static function printResult($format = 'html', $medium = 'both') {
		switch($format) {
			case 'html':
				$output = '';
				self::$_runtimeLogEnabled ? $output .= self::getRuntimeLog('html_table', self::$runtimeLogFilter) : null;
				self::$_performanceLogEnabled ?  $output .= self::getPerformanceLog('html_table') : null;
				break;
			case 'plain':
				$output = '';
				self::$_runtimeLogEnabled ? $output .= print_r(self::getRuntimeLog('array', self::$runtimeLogFilter)) : null;
				self::$_performanceLogEnabled ?  $output .= print_r(self::getPerformanceLog('array')) : null;
				break;
		}
		switch($medium) {
			case 'both':
			case 'display':
				echo $output;
				if($medium != 'both') break;
			case 'file':
				$ext = $format == 'plain' ? 'txt' : 'html';
				file_put_contents('Runtime.'.$ext, $output);
				if($medium != 'both') break;
		}
	}

	// Private ================================================================
	private static function _setParams($params) {
		foreach($params as $property => $value) {
			//Adds the underscore which references the internal property
			$property = '_'.$property;
			//Changes the property if the name is valid:
			if(property_exists($this,$property)):
				$self::${$property} = $value;
				endif; 
		}
	}
	
	private static function _applyParams($params) {
		foreach($params as $key => $value) {
			$method = '_apply'.ucfirst($key);
			if(method_exists($this,$method)) {
				// Will only use static apply methods
				self::$method($value);
			}
		}//~!foreach()
	}
	
	private static function _pushTime( $cmd )
	{
		// Extract seconds and microseconds;
		$mt = microtime();
		list($usec, $sec) = explode(' ', $mt);
		
		// Typecast them to the required types
		$sec = (int) $sec;
		$usec = (float) $usec;
		$usec = (int) ($usec * self::USECDIV);
		
		$cmd = explode(' ',$cmd);
		$state = $cmd[0];
		$name = $cmd[1];
		
		// Set current running state depending on the command
		if($state == 'start') {
			//Check if the timer has already been started
			if(self::$timeRunning[$name] == true) {
				$this->_triggerError('Timer has already been started', E_USER_NOTICE);
				return;
			}
			//Set current state
			self::$timeRunning[$name] = true;
		}
		elseif($state == 'end') {
			//Check if the timer is already stopped
			if(self::$timeRunning[$name] == false) {
				self::_triggerError('Timer has already been stopped/paused',E_USER_NOTICE);
				return;
			}
			self::$timeRunning[$name] = false;
		}
		else {
			self::_triggerError('Invalid command for timer', E_USER_WARNING);
			return;
		}
		
		$time = array($state => array('sec' => $sec, 'usec' => $usec));
		// Add a time entry depending on the command;
		if($state == 'start') {
			self::$timeQueue[$name] = $time;
		}
		elseif($state == 'end') {
			//$count = count($this->timeQueue);
			self::$timeQueue[$name] = array_merge(self::$timeQueue[$name], $time);
		}
	}
}//~!Amfw_Debug()